#!/usr/bin/env bash
# dashboard-report-template -- Manipulate report templates
# > dashboard-report-template ls
# > dashboard-report-template ls-tasks
# > dashboard-report-template get NAME
# > dashboard-report-template put NAME <TEMPLATE_JSON
# > dashboard-report-template delete NAME
#
# Author: Jaeho Shin <netj@cs.stanford.edu>
# Created: 2015-04-29
set -eu

: ${DASHBOARD_TASK_TEMPLATES_INCLUDE_SUPPLIED_PARAMS:=false}

DEEPDIVE_APP=$(find-deepdive-app)
export DEEPDIVE_APP

[[ $# -gt 0 ]] || usage "$0" "No verb given"
Command=$1; shift
case $Command in
    ls|ls-tasks)
        ;;
    get|put|delete)
        [[ $# -gt 0 ]] || usage "$0" "No NAME given"
        TemplateName=$1; shift
        TemplateName=${TemplateName%/}
        ;;
    *)
        usage "$0" "get, put, or delete must be given"
esac

reportTemplateImportOrder=(
"$DEEPDIVE_APP"/snapshot-template
"$MINDBENDER_HOME"/etc/snapshot-template
)

# TODO speed up JSON generation
case $Command in
    ls) # list report templates in JSON array
        echo "["
        find "${reportTemplateImportOrder[@]}" 2>/dev/null \
            \( -name "README.md.in" \
            -o -name "data.sql.in" \
            \) -print |
        sed 's:^.*/snapshot-template/::; s:/[^/]*$::' |
        sort -u |
        while read template; do
            echo ","
            json-string "$template"
        done | tail -n +2
        echo "]"
        ;;

    ls-tasks)
        echo "{"
        find "${reportTemplateImportOrder[@]}" 2>/dev/null \
            -execdir [ -e task.params ] \; \
            \( -name "README.md.in" \
            -o -name "data.sql.in" \
            \) -print |
        sed 's:^.*/snapshot-template/::; s:/[^/]*$::' |
        sort -u |
        while read template; do
            echo ","
            json-string "$template"
            echo ":"
            dashboard-report-template get "$template" type params
        done | tail -n +2
        echo "}"
        ;;

    get) # encode report template into a JSON object
        tmp=$(mktemp -d ${TMPDIR:-/tmp}/dashboard-report-template.XXXXXX)
        trap "rm -rf $tmp" EXIT
        export JSON_FILE=-  # to use report-values for constructing JSON
        echo '{}' >$tmp/params.json

        [[ $# -gt 0 ]] || set -- type params markdownTemplate sqlTemplate

        # TODO It's probably a very good idea to implement this JSON construction in another language as this is already pretty slow.  A small obstacle would be interpreting the report.params files.
        parse-template-parts() {
            local snapshotTemplateRoot=$1; shift
            local template=$1; shift
            local reportTemplatePath="$snapshotTemplateRoot/$template"

            # it has to be a report/task template
            [[ -d "$reportTemplatePath" ]] || return 1
            [[ -e "$reportTemplatePath/README.md.in" ||
               -e "$reportTemplatePath/data.sql.in" ]] || return 1

            local part=
            for part; do
                case $part in
                    type)
                        if [[ -e "$reportTemplatePath"/task.params ]]; then
                            JSON_FILE=$tmp/template.json report-values type=task
                        else
                            JSON_FILE=$tmp/template.json report-values type=report
                        fi
                        ;;

                    params) # encode parameters by interpreting report.params and task.params
                        local inheritedFrom=null
                        required() {
                            local nametype=$1; shift
                            local desc=$1; shift
                            # parse optional type from name
                            local name= type=
                            name=${nametype%:*}
                            type=${nametype#$name}
                            type=${type#:}
                            JSON_FILE=$tmp/params.json report-values \
                                "$name=$(report-values \
                                    type=${type:-null} \
                                    inheritedFrom=$inheritedFrom \
                                    isRequired=true \
                                    description="$desc")"
                        }
                        optional() {
                            local nametype=$1; shift
                            local valueDefault=$1; shift
                            local desc=$1; shift
                            # parse optional type from name
                            local name= type=
                            name=${nametype%:*}
                            type=${nametype#$name}
                            type=${type#:}
                            JSON_FILE=$tmp/params.json report-values \
                                "$name=$(report-values \
                                    type=${type:-null} \
                                    inheritedFrom=$inheritedFrom \
                                    isRequired=false \
                                    defaultValue="$valueDefault" \
                                    description="$desc")"
                        }
                        # taking care of inherited parameters as well
                        ( cd "$snapshotTemplateRoot"
                        list-breadcrumb-paths "$template" |
                        while read path; do
                            (
                            path=${path%/}
                            cd "$path"
                            [[ -e report.params ]] || continue
                            # record provenance of inherited parameters
                            if [[ "$path" = "$TemplateName" ]]
                            then inheritedFrom=null
                            else inheritedFrom=$path
                            fi
                            # interpret report.params
                            source report.params
                            )
                        done
                        )
                        # task templates require special care
                        if [[ -e "$reportTemplatePath"/task.params ]]; then
                            # parameters from any report appearing in the task's scope should be inherited
                            scope() {
                                local scopeType=$1; shift
                                case $scopeType in
                                    report) ;;
                                    *) error "$scopeType: Invalid scope for task $taskPath"
                                esac
                                if $DASHBOARD_TASK_TEMPLATES_INCLUDE_SUPPLIED_PARAMS; then
                                    local scopeValue=
                                    for scopeValue; do
                                        parse-template-parts "$snapshotTemplateRoot" "$scopeValue" params
                                    done
                                fi
                                JSON_FILE=$tmp/template.json report-values \
                                    scope="{$(json-string "$scopeType"):[$(
                                        json-string "$@" | tr '\n' , | sed 's/,$//')]}"
                            }
                            ( cd "$reportTemplatePath"
                            source task.params
                            )
                        fi
                        JSON_FILE=$tmp/template.json report-values \
                            params="$(cat $tmp/params.json)"
                        ;;

                    markdownTemplate) # encode markdownTemplate
                        ( cd "$reportTemplatePath"
                        if [[ -e README.md.in ]]; then
                            JSON_FILE=$tmp/template.json report-values \
                                markdownTemplate="$(cat README.md.in)"
                        fi
                        )
                        ;;

                    sqlTemplate) # encode sqlTemplate
                        ( cd "$reportTemplatePath"
                        if [[ -e data.sql.in ]]; then
                            JSON_FILE=$tmp/template.json report-values \
                                sqlTemplate="$(cat data.sql.in)"
                        fi
                        )
                        ;;
                esac
            done
        }

        # find the snapshot-template
        for snapshotTemplateRoot in "${reportTemplateImportOrder[@]}"; do
            parse-template-parts "$snapshotTemplateRoot" "$TemplateName" "$@" || continue
            break
        done

        # finally, output the template JSON object
        cat $tmp/template.json
        ;;

    put) # decode report template from given JSON object
        reportTemplatePath="$DEEPDIVE_APP"/snapshot-template/"$TemplateName"
        mkdir -p "$reportTemplatePath"
        cd "$reportTemplatePath"

        # clean up existing artifacts before decoding
        rm -f data.sql.in

        cat | # XXX a workaround for ENXIO error coming from using /dev/stdin from nested nodejs processes
        # decode the input JSON object and create corresponding files
        coffee -e '
            fs = require "fs"
            _ = require "underscore"
            [jsonFile,reportTemplateName] = process.argv[4..]

            ## read and parse input report template object
            reportTemplate = JSON.parse fs.readFileSync jsonFile

            ## write files under the report template file
            # XXX string escaping is ugly and very convoluted, but is contained in the next line
            esc = (s) -> "'\''" + (
                    unless s? then ""
                    else (String s).replace /'\''/g, "'\''\\'\'\''"
                ) + "'\''"
            # shorthand for conditionally writing pieces of report template
            write = (fileName, content) ->
                if content?
                    fs.writeFileSync fileName, content
                    yes

            # whether the given path a param comes from overlaps with reportTemplateName
            paramNeeded = (inheritedFrom) ->
                not inheritedFrom? or (
                    reportTemplate.type isnt "task" and
                    (inheritedFrom.substring 0, reportTemplateName.length) isnt reportTemplateName
                )
            # how to format the .params file
            formatParams = (reportTemplate) -> (
                    if (_.size reportTemplate.params) > 0
                        for name,{type,isRequired,defaultValue,description,inheritedFrom} of reportTemplate.params when paramNeeded inheritedFrom
                            nametype = "#{esc name}#{if type? then ":#{esc type}" else ""}"
                            if isRequired
                                "required #{nametype} #{esc description}"
                            else
                                "optional #{nametype} #{esc defaultValue} #{esc description}"
                )?.join "\n"

            # write the parameter declarations
            if reportTemplate.type is "task"
                write "task.params",
                    # write a scope line for task templates
                    ((
                        for scopeType,scopeValues of reportTemplate.scope
                            ["scope", scopeType, (esc v for v in scopeValues)...].join " "
                     )?.join "\n") + "\n" +
                    (formatParams reportTemplate)
            else
                # ordinary report templates simply need parameter declarations
                write "report.params", (formatParams reportTemplate)

            # write the contents
            write "README.md.in", reportTemplate.markdownTemplate
            write "data.sql.in", reportTemplate.sqlTemplate

            # keep chart parameters
            write "report.chart",
                JSON.stringify reportTemplate.chart if reportTemplate.chart?
        ' /dev/stdin "$TemplateName"

        # clean up conflicting artifacts after decoding
        if [[ -e data.sql.in ]]; then
            # place the default report.sh when there's a data.sql.in
            cp -f "$MINDBENDER_HOME"/etc/report-sql-template.sh report.sh
        else
            rm -f report.sh
        fi
        ;;

    delete) # delete given report template
        reportTemplatePath="$DEEPDIVE_APP"/snapshot-template/"$TemplateName"
        [[ -d "$reportTemplatePath" ]] ||
            error "$TemplateName: No deletable report template found"
        rm -rf "$reportTemplatePath"
        ;;
esac
